# -*- coding: utf-8 -*-
# -*- frozen_string_literal: true -*-

require "tmpdir"
require "stringio"
require "tempfile"
require "fileutils"
require "minitest/pride"
require "minitest/autorun"
require_relative "./../scaffold_gem"

ENV["GEM_SCAFFOLD_ENV"] = "test"

class TestScaffold < Minitest::Test

  parallelize_me!

  def setup
    @details = ScaffoldDetails.new ScaffoldCLI::ScaffoldOptions.new
  end

  def test_has_templates
    Dir.mktmpdir do |dir|
      sub_dir = "#{dir}/sub_dir"
      Dir.mkdir sub_dir
      template_a = Tempfile.new %w[template_a .rb.erb], dir
      template_b = Tempfile.new %w[template_b .erb], sub_dir

      templates = Scaffold.new(@details, directory: dir).templates

      assert_includes templates, template_a.path
      assert_includes templates, template_b.path
    end
  end

  def test_can_fill_a_template
    Tempfile.open do |template|
      template.write "<%= @scaffold.namespace %>"
      template.rewind

      @details.stub :namespace, "A" do
        scaffold = Scaffold.new @details, directory: ""
        scaffold.fill(template)
        assert_equal "A", template.read
      end
    end
  end

  def test_fills_templates_in_given_directory
    Dir.mktmpdir do |dir|
      Tempfile.new %w[template_a .rb.erb], dir

      scaffold = Scaffold.new @details, directory: dir
      assert_respond_to scaffold, :fill_templates
    end
  end

  def test_renames_templates
    Dir.mktmpdir do |dir|
      Dir.chdir(dir) { |tmp_dir| # <- can't tempfile#unlink
        template = Tempfile.new %w[template .rb.erb], tmp_dir
        scaffold = Scaffold.new @details, directory: tmp_dir
        expected_file = template.path.sub %r"\.erb\z", ""

        scaffold.rename_templates

        refute File.exist? template.path
        assert File.exist? expected_file
      }
    end
  end

  def test_renames__gem_name_files
    Dir.mktmpdir do |dir|
      # init_setup
      bin = "/bin"
      test = "/test"
      bin_dir = dir + bin
      test_bin_dir = dir + test + bin
      FileUtils.mkdir bin_dir
      FileUtils.mkdir_p test_bin_dir

      old_gemspec = "#{dir}/_gem_name.gemspec"
      old_binstub = "#{bin_dir}/_gem_name-alpine"
      old_test_binstub = "#{test_bin_dir}/test__gem_name.rb"
      old_files = [old_gemspec, old_binstub, old_test_binstub]
      FileUtils.touch old_files
      # end_setup

      scaffold = Scaffold.new @details, directory: dir

      @details.stub :namespace, "SomeGem::SomePlugin" do
        scaffold.rename_gem_files

        old_files.each { |file| refute File.exist?(file) }
        assert File.exist?("#{dir}/some_gem-some_plugin.gemspec")
        assert File.exist?("#{bin_dir}/some_gem-some_plugin-alpine")
        assert File.exist?("#{test_bin_dir}/test_some_gem-some_plugin.rb")
      end #stub
    end #tmpdir
  end

  def test_renames__gem_basename_files
    Dir.mktmpdir do |dir|
      # init_setup
      lib = "/lib"
      test = "/test"
      base = "/_gem_basename"

      lib_dir = dir + lib
      lib_base_dir = lib_dir + base
      test_lib_dir = dir + test + lib
      test_lib_base_dir = test_lib_dir + base
      old_dirs = [lib_base_dir, test_lib_base_dir]
      FileUtils.mkdir_p old_dirs

      old_base = "#{lib_dir + base}.rb"
      old_doc = "#{lib_base_dir}/doc.rb"
      old_test_base = "#{test_lib_dir + test}__gem_basename.rb"
      old_test_doc = "#{test_lib_base_dir + test}_doc.rb"
      old_files = [old_base, old_doc, old_test_base, old_test_doc]
      FileUtils.touch old_files
      # end_setup

      scaffold = Scaffold.new @details, directory: dir

      @details.stub :namespace, "SomeGem::SomePlugin" do
        scaffold.rename_gem_files

        old_files.each { |file| refute File.exist?(file) }
        old_dirs.each { |d| refute Dir.exist?(d) }

        assert File.exist?("#{lib_dir}/some_plugin.rb")
        assert File.exist?("#{lib_dir}/some_plugin/doc.rb")
        assert File.exist?("#{test_lib_dir}/test_some_plugin.rb")
        assert File.exist?("#{test_lib_dir}/some_plugin/test_doc.rb")
      end
    end
  end

  def test_cleans_up_scaffolding
    Dir.mktmpdir do |dir|
      # init_setup
      bin = "/bin"
      bin_dir = dir + bin
      test_dir = "#{dir}/test"
      test_bin_dir = test_dir + bin
      scaffolder_git = "#{dir}/.git/fake_refs"
      scaffolder_dirs = [bin_dir, test_bin_dir, scaffolder_git]
      FileUtils.mkdir_p scaffolder_dirs

      scaffolder = "#{dir}/scaffold_gem.rb"
      read_me = "#{dir}/ReadMe.org"
      change_log = "#{dir}/ChangeLog.org"
      test_scaffolder = "#{test_dir}/test_scaffold_gem.rb"
      ci_file = "#{dir}/.scaffold-ci.yml"
      scaffolding =
        [change_log, read_me, scaffolder, test_scaffolder, ci_file]
      fake_gem_file = Tempfile.create.path
      FileUtils.touch scaffolding
      # end_setup

      Scaffold.new(@details, directory: dir).clean_up

      scaffolding.each { |file| refute File.exist?(file) }
      scaffolder_dirs.each { |d| refute Dir.exist?(d) }
      assert File.exist? fake_gem_file
      assert Dir.exist? test_dir
    end
  end

  def test_only_cleans_up_scaffolding_files_when_bin_requested
    Dir.mktmpdir do |dir|
      bin_dir = "#{dir}/bin"
      Dir.mkdir bin_dir

      scaffolder = "#{dir}/scaffold_gem.rb"
      FileUtils.touch scaffolder

      @details.stub :bin, true do
        Scaffold.new(@details, directory: dir).clean_up

        assert Dir.exist? bin_dir
        refute File.exist? scaffolder
      end
    end
  end

  def test_cleans_up_plugin_path
    Dir.mktmpdir do |dir|
      # init_setup
      main_basename = "/some_gem"
      plugin_basename = "/some_plugin"
      lib_dir = "#{dir}/lib"
      gem_dir = lib_dir + plugin_basename
      test_lib_dir = "#{dir}/test/lib"
      test_gem_dir = test_lib_dir + plugin_basename
      FileUtils.mkdir_p [gem_dir, test_gem_dir]

      plugin = "/some_plugin.rb"
      doc = "/doc.rb"
      test_plugin = "/test_some_plugin.rb"
      test_doc = "/test_doc.rb"

      old_plugin = lib_dir + plugin
      old_doc = gem_dir + doc
      old_test_plugin = test_lib_dir + test_plugin
      old_test_doc = test_gem_dir + test_doc
      old_files = [old_plugin, old_test_plugin, old_doc, old_test_doc]
      FileUtils.touch old_files
      # end_setup

      @details.stub :bin, true do
        @details.stub :namespace, "SomeGem::SomePlugin" do
          Scaffold.new(@details, directory: dir).clean_up

          old_files.each { |file| refute File.exist?(file) }
          assert Dir.exist?(lib_dir + main_basename)
          assert Dir.exist?(test_lib_dir + main_basename)
          assert File.exist?(lib_dir + main_basename + plugin)
          assert File.exist?(test_lib_dir + main_basename + plugin_basename + test_doc)
        end # namespace stub
      end # bin stub
    end # tmpdir
  end

  def test_scaffolds_a_gem
    assert_respond_to Scaffold, :gem
  end
end

class TestScaffoldDetails < Minitest::Test
  parallelize_me!

  def setup
    @scaffold_options = ScaffoldCLI::ScaffoldOptions.new
  end

  def test_has_namespace
    @scaffold_options.stub :namespace, "A" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "A", details.namespace
    end
  end

  def test_has_author
    @scaffold_options.stub :author, "B" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "B", details.author
    end
  end

  def test_has_author_email
    @scaffold_options.stub :email, "o@ah.com" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "o@ah.com", details.email
    end
  end

  def test_has_repo_url
    @scaffold_options.stub :repo, "https://repo.com" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "https://repo.com", details.repo
    end
  end

  def test_has_license
    @scaffold_options.stub :license, "ISC" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "ISC", details.license
    end
  end

  def test_has_bin
    @scaffold_options.stub :bin, true do
      details = ScaffoldDetails.new @scaffold_options
      assert details.bin
    end
  end

  def test_has_main_namespace_given_a_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SomeGem", details.main_namespace
    end
  end

  def test_has_main_namespace_given_a_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SomeGem", details.main_namespace
    end
  end

  def test_has_plugin_namespace_given_a_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SomePlugin", details.plugin_namespace
    end
  end

  def test_plugin_namespace_returns_nil_given_a_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_nil details.plugin_namespace
    end
  end

  def test_has_gem_namespace_given_a_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SomeGem", details.gem_namespace
    end
  end

  def test_has_gem_namespace_given_a_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SomePlugin", details.gem_namespace
    end
  end

  def test_has_gem_name_for_some_gem
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem", details.gem_name
    end
  end

  def test_has_gem_name_for_some_gem_plugin
    @scaffold_options.stub :namespace, "SomeGem::Plugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem-plugin", details.gem_name
    end
  end

  def test_has_gem_name_for_some_plugin_for_some_gem
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem-some_plugin", details.gem_name
    end
  end

  def test_has_gem_constant_for_some_gem
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SOME_GEM", details.gem_constant
    end
  end

  def test_has_gem_constant_for_some_gem_plugin
    @scaffold_options.stub :namespace, "SomeGem::Plugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "PLUGIN", details.gem_constant
    end
  end

  def test_has_gem_constant_for_some_plugin_for_some_gem
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "SOME_PLUGIN", details.gem_constant
    end
  end

  def test_has_formatted_name_for_some_gem
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "Some Gem", details.formatted_name
    end
  end

  def test_has_formatted_name_for_some_gem_plugin
    @scaffold_options.stub :namespace, "SomeGem::Plugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "Some Gem - Plugin", details.formatted_name
    end
  end

  def test_has_formatted_name_for_some_plugin_for_some_gem
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "Some Gem - Some Plugin", details.formatted_name
    end
  end

  def test_has_same_formatted_name_as_given
    scaffold_options =
      ScaffoldCLI::ScaffoldOptions.new namespace: "A", formatted_name: "B"
    details = ScaffoldDetails.new scaffold_options
    assert_equal "B", details.formatted_name
  end

  def test_has_main_basename
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem", details.main_basename
    end
  end

  def test_has_plugin_basename
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_plugin", details.plugin_basename
    end
  end

  def test_has_gem_basename_given_a_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem", details.gem_basename
    end
  end

  def test_has_gem_basename_given_a_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_plugin", details.gem_basename
    end
  end

  def test_has_gem_path_given_main_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem", details.gem_path
    end
  end

  def test_has_gem_path_given_plugin_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal "some_gem/some_plugin", details.gem_path
    end
  end

  def test_prints_require_main_given_a_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require "some_gem"', details.require_main
    end
  end

  def test_prints_require_main_given_a_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require "some_gem"', details.require_main
    end
  end

  def test_can_print_required_dependency_given_a_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require "some_gem/some_plugin"', details.require_plugin
    end
  end

  def test_does_not_print_required_dependency_given_a_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_nil details.require_plugin
    end
  end

  def test_prints_toplevel_require_minitest_config_with_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require_relative "./../_config/minitest"',
        details.toplevel_require_minitest_config
    end
  end

  def test_prints_toplevel_require_minitest_config_with_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require_relative "./../../_config/minitest"',
        details.toplevel_require_minitest_config
    end
  end

  def test_prints_sublevel_require_minitest_config_with_simple_namespace
    @scaffold_options.stub :namespace, "SomeGem" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require_relative "./../../_config/minitest"',
        details.sublevel_require_minitest_config
    end
  end

  def test_prints_sublevel_require_minitest_config_with_contextual_namespace
    @scaffold_options.stub :namespace, "SomeGem::SomePlugin" do
      details = ScaffoldDetails.new @scaffold_options
      assert_equal 'require_relative "./../../../_config/minitest"',
        details.sublevel_require_minitest_config
    end
  end
end

class TestScaffoldCLI < Minitest::Test

  parallelize_me!

  def setup
    @argv = -> options { Array[StringIO.new(options).read.split] }
  end

  def test_shows_help_given_no_options
    assert_output(%r"\Ausage"i) {
      cli = ScaffoldCLI.new
      cli.parse @argv.("")
    }
  end

  def test_h__help_options_show_usage_info
    assert_output(%r"\Ausage"i) {
      cli = ScaffoldCLI.new
      cli.parse @argv.("-h")
    }

    assert_output(%r"\Ausage"i) {
      cli = ScaffoldCLI.new
      cli.parse @argv.("--help")
    }
  end

  def test_returns_scaffold_options_given_mandatory_options
    options = nil
    _ = capture_io {
      cli = ScaffoldCLI.new
      options = cli.parse @argv.("-n A -a B -e e@ah.com -r https://s.com -l ISC")
    }

    assert options.frozen?
    assert_equal "A", options.namespace
    assert_equal "B", options.author
    assert_equal "e@ah.com", options.email
    assert_equal "https://s.com", options.repo
    assert_equal "ISC", options.license
    assert_equal "", options.formatted_name
    assert_equal false, options.bin
  end

  def test_fails_given_invalid_email
    cli = ScaffoldCLI.new
    assert_raises { cli.parse @argv.("-n A -a B -e eah.com -r https://s.com -l ISC") }
  end

  def test_fails_given_invalid_repo_url
    cli = ScaffoldCLI.new
    assert_raises { cli.parse @argv.("-n A -a B -e e@ah.com -r =https://s.com -l ISC") }
  end

  def test_fails_when_mandatory_option__namespace_is_missing
    assert_output(nil, "Must provide all mandatory options.\n") {
      cli = ScaffoldCLI.new
      cli.parse @argv.("-a B -e e@ah.com -r https://s.com -l ISC")
    }
  end

  def test_fails_when_mandatory_option__author_is_missing
    assert_output(nil, "Must provide all mandatory options.\n") {
      cli = ScaffoldCLI.new
      cli.parse @argv.("-n A -e e@ah.com -r https://s.com -l ISC")
    }
  end

  def test_fails_when_mandatory_option__email_is_missing
    assert_output(nil, "Must provide all mandatory options.\n") {
      cli = ScaffoldCLI.new
      cli.parse @argv.("-n A -a B -r https://s.com -l ISC")
    }
  end

  def test_fails_when_mandatory_option__repo_is_missing
    assert_output(nil, "Must provide all mandatory options.\n") {
      cli = ScaffoldCLI.new
      cli.parse @argv.("-n A -a B -e e@ah.com -l ISC")
    }
  end

  def test_fails_when_mandatory_option__license_is_missing
    assert_output(nil, "Must provide all mandatory options.\n") {
      cli = ScaffoldCLI.new
      cli.parse @argv.("-n A -a B -e e@ah.com -r https://s.com")
    }
  end

  def test_b__bin_flags_set_bin_option_to_true
    options = nil
    _ = capture_io {
      cli = ScaffoldCLI.new
      mandatory = "-n A -a B -e e@ah.com -r https://s.com -l ISC"
      options = cli.parse @argv.("-b " + mandatory)
    }
    assert options.bin
  end

  def test_fn__formatted_name_set_given_text_into_the_options
    options = nil
    _ = capture_io {
      cli = ScaffoldCLI.new
      mandatory = "-n A -a B -e e@ah.com -r https://s.com -l ISC"
      options = cli.parse @argv.("-f a " + mandatory)
    }
    assert_equal "a", options.formatted_name
  end

  def test_works_with_long_version_of_options
    options = nil
    _ = capture_io {
      cli = ScaffoldCLI.new
      options = cli.parse @argv.("--namespace=A" +
        " --author=B" +
        " --email=e@ah.com" +
        " --repo=https://s.com" +
        " --license=ISC" +
        " --bin" +
        " --formatted-name=a" )
    }

    assert_equal "A", options.namespace
    assert_equal "B", options.author
    assert_equal "e@ah.com", options.email
    assert_equal "https://s.com", options.repo
    assert_equal "ISC", options.license
    assert options.bin
    assert_equal "a", options.formatted_name
  end
end

class TestScaffoldedGem < Minitest::Test
  def test_generated_files_can_be_packed_as_a_gem
    skip "Integration Test" unless ENV["SCAFFOLD_CI"]

    Dir.mktmpdir { |tmp_dir|
      templates = Dir.children "#{__dir__}/../"
      templates -= [".DS_Store"] # macOS junk file
      FileUtils.cp_r templates, tmp_dir

      FileUtils.cd(tmp_dir) do |dir|
        _, err = capture_subprocess_io {
          `ruby scaffold_gem.rb -n SomeGem::SomePlugin -b -a yo -e yo@email.com -r https://something.com -l ISC`
          `bundle install --path .bundle/gems`
          `bundle exec rake`
        }

        assert_match %r"(\A\z|warning)", err
        assert_equal 0, $?.to_i
      end
    }
  end
end
